package cn.com.connext.scrm.service;

import cn.com.connext.scrm.config.Constants;
import cn.com.connext.scrm.domain.master.CrmAuthorizerToken;
import cn.com.connext.scrm.repository.master.CrmAuthorizerTokenRepository;
import cn.com.connext.scrm.service.dto.AuthorizationInfoDTO;
import cn.com.connext.scrm.service.dto.AuthorizerRefreshTokenDTO;
import cn.com.connext.scrm.service.dto.JsonResult;
import cn.com.connext.scrm.service.dto.WechatConfig;
import cn.com.connext.scrm.service.mapper.ThirdPartyService;
import cn.com.connext.scrm.utils.RedisUtils;
import cn.com.connext.scrm.utils.wechat.CardSignature;
import cn.com.connext.scrm.utils.wechat.WXBizMsgCrypt;
import cn.com.connext.scrm.web.rest.vm.ApiAuthorizerTokenVM;
import cn.com.connext.scrm.web.rest.vm.ApiComponentTokenVM;
import cn.com.connext.scrm.web.rest.vm.ApiCreatePreauthcodeVM;
import cn.com.connext.scrm.web.rest.vm.ApiQueryAuthVM;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.ByteArrayInputStream;

import java.io.InputStream;
import java.io.StringReader;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@Service
public class ThirdPartyServiceImpl implements ThirdPartyService {
    private static final Logger logger = LoggerFactory.getLogger(ThirdPartyServiceImpl.class);

    @Autowired
    private RedisUtils redisUtils;

    private RestTemplate restTemplate = new RestTemplate();

    private static final long ONE_DAY = 60 * 90L;

    private static final String TOKEN = "4c9ec5b668844069a1e01093cef26932";//消息校验Token

    private static final String ENCODING_AES_KEY = "4c9ec5b668844069a1e01093cef269321234567eww1";//为消息加解密Key

    private static final String THIRD_PARTY_APP_ID = "wx1b9d1bd279eb17ac";//为第三方平台的app_id

    private static final String COMPONENT_APP_SECRET = "2b44be9af12d5ebef1bab6ddb2c1b3cd";

    private static final String AUTHORIZER_APPID = "wxc1c6e200877c8bdc"; //帝亚吉欧会员俱乐部
//    private static final String AUTHORIZER_APPID = "wx396e3ba737824789"; //LoveWhisky

    @Autowired
    private CrmAuthorizerTokenRepository crmAuthorizerTokenRepository;


    @Override
    public void getComponentVerifyTicket(String timestamp, String nonce, String msgSignature, String postData) throws Exception {
        // 需要加密的明文
        WXBizMsgCrypt pc = new WXBizMsgCrypt(TOKEN, ENCODING_AES_KEY, THIRD_PARTY_APP_ID);
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        String FEATURE = null;
        FEATURE = "http://apache.org/xml/features/disallow-doctype-decl";
        dbf.setFeature(FEATURE, true);
        FEATURE = "http://xml.org/sax/features/external-general-entities";
        dbf.setFeature(FEATURE, false);
        FEATURE = "http://xml.org/sax/features/external-parameter-entities";
        dbf.setFeature(FEATURE, false);
        FEATURE = "http://apache.org/xml/features/nonvalidating/load-external-dtd";
        dbf.setFeature(FEATURE, false);
        dbf.setXIncludeAware(false);
        dbf.setExpandEntityReferences(false);
        DocumentBuilder db = dbf.newDocumentBuilder();
        StringReader sr = new StringReader(postData);
        InputSource is = new InputSource(sr);
        Document document = db.parse(is);

        Element root = document.getDocumentElement();
        NodeList nodelist1 = root.getElementsByTagName("Encrypt");
        String encrypt = nodelist1.item(0).getTextContent();
        String format = "<xml><ToUserName><![CDATA[toUser]]></ToUserName><Encrypt><![CDATA[%1$s]]></Encrypt></xml>";
        String fromXML = String.format(format, encrypt);
        String result = pc.DecryptMsg(msgSignature, timestamp, nonce, fromXML);
        logger.info("解密后: " + result);
        //获取ComponentVerifyTicket
        Map<String, String> xmlMap = xmlToMap(result);
        String componentVerifyTicket = xmlMap.get("ComponentVerifyTicket");
        if (StringUtils.isNotBlank(componentVerifyTicket)) {
            //获取ticket,没有则为authorized事件
            redisUtils.set("ComponentVerifyTicket", componentVerifyTicket);
            logger.info("ComponentVerifyTicket: " + componentVerifyTicket);
        }
    }

    @Override
    public String bindcomponent() {
        //判断缓存是否有预授权码  没有通过票据和三方授权凭证获取
        //令牌（component_access_token）是第三方平台接口的调用凭据。令牌的获取是有限制的，每个令牌的有效期为 2 小时
        String accessToken = getComponentAccessToken();
        String detailKey = "API_COMPONENT_TOKEN";
        HttpHeaders headers = new HttpHeaders();
        MediaType type = MediaType.parseMediaType("application/json; charset=UTF-8");
        headers.setContentType(type);
        String pre_auth_code_key = "PRE_AUTH_CODE";
        String pre_auth_code = null;
        if (!redisUtils.exists(pre_auth_code_key)) {
            //预授权码（pre_auth_code）是第三方平台方实现授权托管的必备信息，每个预授权码有效期为 10 分钟。需要先获取令牌才能调用  1800
            ApiCreatePreauthcodeVM apiCreatePreauthcodeVM = new ApiCreatePreauthcodeVM();
            apiCreatePreauthcodeVM.setComponent_access_token(accessToken);//三方平台授权码
            apiCreatePreauthcodeVM.setComponent_appid(THIRD_PARTY_APP_ID);//三方平台appid
            HttpEntity httpEntity = new HttpEntity(JSON.toJSONString(apiCreatePreauthcodeVM), headers);
            String apiCreatePreauthcodeUrl = Constants.Api_Create_Preauthcode.replace("COMPONENT_ACCESS_TOKEN", accessToken);
            JSONObject result = restTemplate.postForObject(apiCreatePreauthcodeUrl, httpEntity, JSONObject.class);
            pre_auth_code = result.getString("pre_auth_code");//授权码
            if (StringUtils.isNotBlank(pre_auth_code)) {
                redisUtils.set(pre_auth_code_key, pre_auth_code, 30 * 50L);//1800
                logger.info("CRMAccessToken={}", String.valueOf(redisUtils.get(detailKey)));
            }
        } else {
            pre_auth_code = String.valueOf(redisUtils.get(pre_auth_code_key));
        }
        String requestUrls = Constants.BindComponent;
        //授权回调地址 REDIRECT_URL
        String redirect_uri = "https://wxadmin.singlemalts.com.cn/gateway/note-service/wx/threeSide/queryAuth";
        requestUrls = requestUrls.replace("COMPONENT_APPID", THIRD_PARTY_APP_ID).replace("PRE_AUTH_CODE", pre_auth_code).replace("REDIRECT_URL", redirect_uri);
        return requestUrls;
    }

    @Override
    public void queryAuth(String authCode, String expiresIn) {
        //使用授权码获取授权信息
        String accessToken = getComponentAccessToken();//三方令牌
        MediaType type = MediaType.parseMediaType("application/json; charset=UTF-8");
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(type);
        ApiQueryAuthVM apiQueryAuthVM = new ApiQueryAuthVM();
        apiQueryAuthVM.setComponent_appid(THIRD_PARTY_APP_ID);
        apiQueryAuthVM.setComponent_access_token(accessToken);
        apiQueryAuthVM.setAuthorization_code(authCode);
        HttpEntity httpEntity = new HttpEntity(JSON.toJSONString(apiQueryAuthVM), headers);
        String reqUrl = Constants.Api_Query_Auth.replace("COMPONENT_ACCESS_TOKEN", accessToken);
        JSONObject result = restTemplate.postForObject(reqUrl, httpEntity, JSONObject.class);
        logger.info("authorization_info——Result:{}", JSON.toJSONString(result));
        Object object = result.getJSONObject("authorization_info");
        AuthorizationInfoDTO authorizationInfoDTO = JSON.parseObject(JSON.toJSONString(object), AuthorizationInfoDTO.class);
        logger.info("授权信息authorizationInfoDTO={}", JSON.toJSONString(authorizationInfoDTO));
        //刷新令牌（在授权的公众号具备API权限时，才有此返回值），刷新令牌主要用于第三方平台获取和刷新已授权用户的 authorizer_access_token。
        // 一旦丢失，只能让用户重新授权，才能再次拿到新的刷新令牌。用户重新授权后，之前的刷新令牌会失效
        //缓存保存公众号授权凭证  和刷新令牌
        String authorizer_access_token = "AUTHORIAER_ACCESS_TOKEN";
        String authorizer_refresh_token = "AUTHORIZER_REFRESH_TOKEN";
        if (StringUtils.isNotBlank(authorizationInfoDTO.getAuthorizer_access_token())) {
            redisUtils.set(authorizer_access_token, authorizationInfoDTO.getAuthorizer_access_token(), authorizationInfoDTO.getExpires_in());//3600
            logger.info("CRMAccessToken={}", String.valueOf(redisUtils.get(authorizer_access_token)));
        }
        if (StringUtils.isNotBlank(authorizationInfoDTO.getAuthorizer_refresh_token())) {
            redisUtils.set(authorizer_refresh_token, authorizationInfoDTO.getAuthorizer_refresh_token());//1800
            CrmAuthorizerToken crmAuthorizerToken = new CrmAuthorizerToken();
            crmAuthorizerToken.setAuthorizerRefreshToken(authorizationInfoDTO.getAuthorizer_refresh_token());
            crmAuthorizerToken.setCreateTime(new Date());
            crmAuthorizerTokenRepository.saveAndFlush(crmAuthorizerToken);
            logger.info("authorizer_refresh_token={}", String.valueOf(redisUtils.get(authorizer_refresh_token)));
        }

    }

    @Override
    public JsonResult getCardSignature(String req) throws Exception {
        //判断缓存是否有公众号授权码  没有根据刷新凭证获取授权码
        String authorizer_access_token = "AUTHORIAER_ACCESS_TOKEN";
        String authorizer_refresh_token = "AUTHORIZER_REFRESH_TOKEN";
        String authorizerAccessToken = null;
        JsonResult jsonResult = new JsonResult();
        if (!redisUtils.exists(authorizer_access_token)) {
            String authorizerRefreshToken;
            if (!redisUtils.exists(authorizer_refresh_token)) {
                //无刷新凭证  公众号未授权  需要重新授权
                authorizerRefreshToken = crmAuthorizerTokenRepository.getLastRefreshToken();
                logger.info("authorizerRefreshToken 1={}", authorizerRefreshToken);
            } else {
                authorizerRefreshToken = String.valueOf(redisUtils.get(authorizer_refresh_token));
                logger.info("authorizerRefreshToken 2={}", authorizerRefreshToken);
            }
            HttpHeaders headers = new HttpHeaders();
            MediaType type = MediaType.parseMediaType("application/json; charset=UTF-8");
            headers.setContentType(type);
            String accessToken = getComponentAccessToken();
            logger.info("accessToken 1={}", accessToken);
            ApiAuthorizerTokenVM apiAuthorizerTokenVM = new ApiAuthorizerTokenVM();
            apiAuthorizerTokenVM.setComponent_access_token(accessToken);//令牌
            apiAuthorizerTokenVM.setComponent_appid(THIRD_PARTY_APP_ID);//第三方平台 appid
            apiAuthorizerTokenVM.setAuthorizer_appid(AUTHORIZER_APPID);//授权方 appid
            apiAuthorizerTokenVM.setAuthorizer_refresh_token(authorizerRefreshToken);//刷新令牌，获取授权信息时得到
            HttpEntity httpEntity = new HttpEntity(JSON.toJSONString(apiAuthorizerTokenVM), headers);
            String requestUrl = Constants.Api_Authorizer_Token.replace("COMPONENT_ACCESS_TOKEN", accessToken);
            logger.info(" start request URL = {} ", requestUrl);
            logger.info(" httpEntity:{}", JSON.toJSONString(httpEntity));
            JSONObject result = restTemplate.postForObject(requestUrl, httpEntity, JSONObject.class);
            AuthorizerRefreshTokenDTO authorizerRefreshTokenDTO = JSON.parseObject(JSON.toJSONString(result), AuthorizerRefreshTokenDTO.class);
            logger.info(" authorizerRefreshTokenDTO = {} ", JSON.toJSONString(authorizerRefreshTokenDTO));
            authorizerAccessToken = authorizerRefreshTokenDTO.getAuthorizer_access_token();
            //公众号授权保存
            if (StringUtils.isNotBlank(authorizerAccessToken)) {
                redisUtils.set(authorizer_access_token, authorizerAccessToken, 60 * 90L);//1800
                logger.info("authorizer_access_token={}", String.valueOf(redisUtils.get(authorizer_access_token)));
            }
            //公众号刷新码保存
            if (StringUtils.isNotBlank(authorizerRefreshTokenDTO.getAuthorizer_refresh_token())) {
                redisUtils.set(authorizer_refresh_token, authorizerRefreshTokenDTO.getAuthorizer_refresh_token());//1800
                CrmAuthorizerToken crmAuthorizerToken = new CrmAuthorizerToken();
                crmAuthorizerToken.setAuthorizerRefreshToken(authorizerRefreshTokenDTO.getAuthorizer_refresh_token());
                crmAuthorizerToken.setCreateTime(new Date());
                crmAuthorizerTokenRepository.saveAndFlush(crmAuthorizerToken);
                logger.info("authorizer_refresh_token={}", String.valueOf(redisUtils.get(authorizer_refresh_token)));
            }
        } else {
            authorizerAccessToken = String.valueOf(redisUtils.get(authorizer_access_token));
        }
        logger.info(" 公众号授权凭证authorizerAccessToken = {} ", authorizerAccessToken);
        WechatConfig wechatConfig = CardSignature.getCardSignature(req, AUTHORIZER_APPID, authorizerAccessToken);
        jsonResult.setData(wechatConfig);
        jsonResult.setState(0);
        return jsonResult;
    }

    public String getComponentAccessToken() {
        String accessToken = null;
        String detailKey = "API_COMPONENT_TOKEN";
        HttpHeaders headers = new HttpHeaders();
        MediaType type = MediaType.parseMediaType("application/json; charset=UTF-8");
        headers.setContentType(type);
        //获取三方令牌 7200
        if (!redisUtils.exists(detailKey)) {
            String requestUrl = Constants.API_COMPONENT_TOKEN;
            ApiComponentTokenVM apiComponentTokenVM = new ApiComponentTokenVM();
            apiComponentTokenVM.setComponent_appid(THIRD_PARTY_APP_ID);//第三方平台 appid
            apiComponentTokenVM.setComponent_appsecret(COMPONENT_APP_SECRET);//第三方平台 appsecret
            String componentVerifyTicket = String.valueOf(redisUtils.get("ComponentVerifyTicket"));
            logger.info("appid:{}, appsecret:{}, componentVerifyTicket:{}", THIRD_PARTY_APP_ID, COMPONENT_APP_SECRET, componentVerifyTicket);
            apiComponentTokenVM.setComponent_verify_ticket(componentVerifyTicket);//微信后台推送的 ticket
            HttpEntity httpEntity = new HttpEntity(JSON.toJSONString(apiComponentTokenVM), headers);
            logger.info(" start request URL = {} ", requestUrl);
            JSONObject result = restTemplate.postForObject(requestUrl, httpEntity, JSONObject.class);
            logger.info("response url = {}", result.toString());
            if (!result.containsKey("component_access_token")) {
                logger.info("获取token错误：{}", result.getString("errmsg"));
            }
            accessToken = result.getString("component_access_token");
            if (StringUtils.isNotBlank(accessToken)) {
                redisUtils.set(detailKey, accessToken, 60 * 60L);//7200
                logger.info("CRMAccessToken={}", String.valueOf(redisUtils.get(detailKey)));
            }
        } else {
            accessToken = String.valueOf(redisUtils.get(detailKey));
        }
        return accessToken;
    }

    //通用的。返回map格式

    /**
     * XML格式字符串转换为Map
     *
     * @param strXML XML字符串
     * @return XML数据转换后的Map
     * @throws Exception
     */
    public static Map<String, String> xmlToMap(String strXML) throws Exception {
        try {
            Map<String, String> data = new HashMap<String, String>();
            DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
            InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
            Document doc = documentBuilder.parse(stream);
            doc.getDocumentElement().normalize();
            NodeList nodeList = doc.getDocumentElement().getChildNodes();
            for (int idx = 0; idx < nodeList.getLength(); ++idx) {
                Node node = nodeList.item(idx);
                if (node.getNodeType() == Node.ELEMENT_NODE) {
                    Element element = (Element) node;
                    data.put(element.getNodeName(), element.getTextContent());
                }
            }
            try {
                stream.close();
            } catch (Exception ex) {
                // do nothing
            }
            return data;
        } catch (Exception ex) {
            logger.info("xmlToMap转换异常: ");
            throw ex;
        }
    }
}
